// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef NET_WEBSOCKETS_WEBSOCKET_INFLATER_H_
#define NET_WEBSOCKETS_WEBSOCKET_INFLATER_H_

#include <stddef.h>

#include <deque>
#include <memory>
#include <utility>
#include <vector>

#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "net/base/net_export.h"

extern "C" struct z_stream_s;

namespace net {

class IOBufferWithSize;

// WebSocketInflater uncompresses data compressed by DEFLATE algorithm.
class NET_EXPORT_PRIVATE WebSocketInflater {
public:
    WebSocketInflater();
    // |input_queue_capacity| is a capacity for each contiguous block in the
    // input queue. The input queue can grow without limit.
    WebSocketInflater(size_t input_queue_capacity, size_t output_buffer_capacity);
    ~WebSocketInflater();

    // Returns true if there is no error.
    // |window_bits| must be between 8 and 15 (both inclusive).
    // This function must be called exactly once before calling any of the
    // following functions.
    bool Initialize(int window_bits);

    // Adds bytes to |stream_|.
    // Returns true if there is no error.
    // If the size of the output data reaches the capacity of the output buffer,
    // the following input data will be "choked", i.e. stored in the input queue,
    // staying compressed.
    bool AddBytes(const char* data, size_t size);

    // Flushes the input.
    // Returns true if there is no error.
    bool Finish();

    // Returns up to |size| bytes of the decompressed output.
    // Returns null if there is an inflation error.
    // The returned bytes will be dropped from the current output and never be
    // returned again.
    // If some input data is choked, calling this function may restart the
    // inflation process.
    // This means that even if you call |Finish()| and call |GetOutput()| with
    // size = |CurrentOutputSize()|, the inflater may have some remaining data.
    // To confirm the inflater emptiness, you should check whether
    // |CurrentOutputSize()| is zero.
    scoped_refptr<IOBufferWithSize> GetOutput(size_t size);

    // Returns the size of the current inflated output.
    size_t CurrentOutputSize() const { return output_buffer_.Size(); }

    static const size_t kDefaultBufferCapacity = 512;
    static const size_t kDefaultInputIOBufferCapacity = 512;

private:
    // Ring buffer with fixed capacity.
    class NET_EXPORT_PRIVATE OutputBuffer {
    public:
        explicit OutputBuffer(size_t capacity);
        ~OutputBuffer();

        size_t Size() const;
        // Returns (tail pointer, availabe size).
        // A user can push data to the queue by writing the data to
        // the area returned by this function and calling AdvanceTail.
        std::pair<char*, size_t> GetTail();
        void Read(char* dest, size_t size);
        void AdvanceTail(size_t advance);

    private:
        void AdvanceHead(size_t advance);

        const size_t capacity_;
        std::vector<char> buffer_;
        size_t head_;
        size_t tail_;
    };

    class InputQueue {
    public:
        // |capacity| is used for the capacity of each IOBuffer in this queue.
        // this queue itself can grow without limit.
        explicit InputQueue(size_t capacity);
        ~InputQueue();

        // Returns (data pointer, size), the first component of unconsumed data.
        // The type of data pointer is non-const because |inflate| function
        // requires so.
        std::pair<char*, size_t> Top();
        bool IsEmpty() const { return buffers_.empty(); }
        void Push(const char* data, size_t size);
        // Consumes the topmost |size| bytes.
        // |size| must be less than or equal to the first buffer size.
        void Consume(size_t size);

    private:
        size_t PushToLastBuffer(const char* data, size_t size);

        const size_t capacity_;
        size_t head_of_first_buffer_;
        size_t tail_of_last_buffer_;
        std::deque<scoped_refptr<IOBufferWithSize>> buffers_;
    };

    int InflateWithFlush(const char* next_in, size_t avail_in);
    int Inflate(const char* next_in, size_t avail_in, int flush);
    int InflateChokedInput();

    std::unique_ptr<z_stream_s> stream_;
    InputQueue input_queue_;
    OutputBuffer output_buffer_;

    DISALLOW_COPY_AND_ASSIGN(WebSocketInflater);
};

} // namespace net

#endif // NET_WEBSOCKETS_WEBSOCKET_INFLATER_H_
